//***************************************************************************************
// d3dApp.cpp by Frank Luna (C) 2011 All Rights Reserved.
//***************************************************************************************

#include "d3dApp.h"
#include <WindowsX.h>
#include <sstream>
#include <string>
#include "resource.h"
#include "Gine.h"
using namespace std;

namespace
{
  // This is just used to forward Windows messages from a global window
  // procedure to our member function window procedure because we cannot
  // assign a member function to WNDCLASS::lpfnWndProc.
  D3DApp* gd3dApp = 0;
}

LRESULT CALLBACK
  MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  // Forward hwnd on because we can get messages (e.g., WM_CREATE)
  // before CreateWindow returns, and thus before mhMainWnd is valid.
  return gd3dApp->MsgProc(hwnd, msg, wParam, lParam);
}

D3DApp::D3DApp(HINSTANCE hInstance)
  :	mhAppInst(hInstance),
  mMainWndCaption(L"D3D11 Application"),
  md3dDriverType(D3D_DRIVER_TYPE_HARDWARE),
  mClientWidth(1280),
  mClientHeight(720),
  mEnable4xMsaa(false),
  mEnableSSAO(false),
  mhMainWnd(0),
  mAppPaused(false),
  mMinimized(false),
  mMaximized(false),
  mResizing(false),
  m4xMsaaQuality(0),
  mFullscreen(false),

  md3dDevice(0),
  md3dImmediateContext(0),
  mSwapChain(0),
  mDepthStencilBuffer(0),
  mRenderTargetView(0),
  mDepthStencilView(0)
{
  ZeroMemory(&mScreenViewport, sizeof(D3D11_VIEWPORT));

  // Get a pointer to the application object so we can forward 
  // Windows messages to the object's window procedure through
  // the global window procedure.
  gd3dApp = this;
}

D3DApp::~D3DApp()
{
  ReleaseCOM(mRenderTargetView);
  ReleaseCOM(mDepthStencilView);
  ReleaseCOM(mSwapChain);
  ReleaseCOM(mDepthStencilBuffer);

  // Restore all default settings.
  if( md3dImmediateContext )
    md3dImmediateContext->ClearState();

  ReleaseCOM(md3dImmediateContext);
  ReleaseCOM(md3dDevice);
}

HINSTANCE D3DApp::AppInst()const
{
  return mhAppInst;
}

HWND D3DApp::MainWnd()const
{
  return mhMainWnd;
}

float D3DApp::AspectRatio()const
{
  return static_cast<float>(mClientWidth) / mClientHeight;
}

int D3DApp::Run()
{
  MSG msg = {0};

  mTimer.Reset();

  while(msg.message != WM_QUIT)
  {
    // If there are Window messages then process them.
    if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
    {
      TranslateMessage( &msg );
      DispatchMessage( &msg );
    }
    // Otherwise, do animation/game stuff.
    else
    {
      mTimer.Tick();

      if(!mAppPaused)
      {
        CalculateFrameStats();
        UpdateScene(mTimer.DeltaTime());	
        DrawScene();
      }
      else
      {
        Sleep(100);
      }
    }
  }

  return (int)msg.wParam;
}

bool D3DApp::Init()
{
  printf("Init main window...\n");
  if(!InitMainWindow()) return false;

  printf("Init Direct3D...\n");
  if(!InitDirect3D()) return false;

  RefreshGlobals();

  return true;
}

void D3DApp::OnResize()
{
  assert(md3dImmediateContext);
  assert(md3dDevice);
  assert(mSwapChain);

  // Release the old views, as they hold references to the buffers we
  // will be destroying.  Also release the old depth/stencil buffer.
  ReleaseCOM(mRenderTargetView);
  ReleaseCOM(mDepthStencilView);
  ReleaseCOM(mDepthStencilBuffer);

  // Resize the swap chain and recreate the render target view.
  HR(mSwapChain->ResizeBuffers(1, mClientWidth, mClientHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0));
  ID3D11Texture2D* backBuffer;
  HR(mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&backBuffer)));
  HR(md3dDevice->CreateRenderTargetView(backBuffer, 0, &mRenderTargetView));
  ReleaseCOM(backBuffer);

  // Create the depth/stencil buffer and view.
  D3D11_TEXTURE2D_DESC depthStencilDesc;

  depthStencilDesc.Width     = mClientWidth;
  depthStencilDesc.Height    = mClientHeight;
  depthStencilDesc.MipLevels = 1;
  depthStencilDesc.ArraySize = 1;
  depthStencilDesc.Format    = DXGI_FORMAT_D24_UNORM_S8_UINT;

  // Use 4X MSAA? --must match swap chain MSAA values.
  if(mEnable4xMsaa)
  {
    depthStencilDesc.SampleDesc.Count   = 4;
    depthStencilDesc.SampleDesc.Quality = m4xMsaaQuality-1;
  }
  // No MSAA
  else
  {
    depthStencilDesc.SampleDesc.Count   = 1;
    depthStencilDesc.SampleDesc.Quality = 0;
  }

  depthStencilDesc.Usage          = D3D11_USAGE_DEFAULT;
  depthStencilDesc.BindFlags      = D3D11_BIND_DEPTH_STENCIL;
  depthStencilDesc.CPUAccessFlags = 0; 
  depthStencilDesc.MiscFlags      = 0;

  HR(md3dDevice->CreateTexture2D(&depthStencilDesc, 0, &mDepthStencilBuffer));
  HR(md3dDevice->CreateDepthStencilView(mDepthStencilBuffer, 0, &mDepthStencilView));

  // Bind the render target view and depth/stencil view to the pipeline.
  md3dImmediateContext->OMSetRenderTargets(1, &mRenderTargetView, mDepthStencilView);

  // Set the viewport transform.
  mScreenViewport.TopLeftX = 0;
  mScreenViewport.TopLeftY = 0;
  mScreenViewport.Width    = static_cast<float>(mClientWidth);
  mScreenViewport.Height   = static_cast<float>(mClientHeight);
  mScreenViewport.MinDepth = 0.0f;
  mScreenViewport.MaxDepth = 1.0f;

  md3dImmediateContext->RSSetViewports(1, &mScreenViewport);

  RefreshGlobals();
}

void D3DApp::SetFullscreen(bool aFullscreen)
{
  mFullscreen = true;
  if(mSwapChain)
    mSwapChain->SetFullscreenState(true, 0);
}

LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg)
  {
    // WM_ACTIVATE is sent when the window is activated or deactivated.  
    // We pause the game when the window is deactivated and unpause it 
    // when it becomes active.  
  case WM_ACTIVATE:
    if(LOWORD(wParam) == WA_INACTIVE)
    {
      mAppPaused = true;
      mTimer.Stop();
    }
    else
    {
      mAppPaused = false;
      mTimer.Start();
    }
    return 0;

    // WM_SIZE is sent when the user resizes the window.  
  case WM_SIZE:
    // Save the new client area dimensions.
    mClientWidth  = LOWORD(lParam);
    mClientHeight = HIWORD(lParam);
    if( md3dDevice )
    {
      if( wParam == SIZE_MINIMIZED )
      {
        mAppPaused = true;
        mMinimized = true;
        mMaximized = false;
      }
      else if( wParam == SIZE_MAXIMIZED )
      {
        mAppPaused = false;
        mMinimized = false;
        mMaximized = true;
        OnResize();
      }
      else if( wParam == SIZE_RESTORED )
      {

        // Restoring from minimized state?
        if( mMinimized )
        {
          mAppPaused = false;
          mMinimized = false;
          OnResize();
        }

        // Restoring from maximized state?
        else if( mMaximized )
        {
          mAppPaused = false;
          mMaximized = false;
          OnResize();
        }
        else if( mResizing )
        {
          // If user is dragging the resize bars, we do not resize 
          // the buffers here because as the user continuously 
          // drags the resize bars, a stream of WM_SIZE messages are
          // sent to the window, and it would be pointless (and slow)
          // to resize for each WM_SIZE message received from dragging
          // the resize bars.  So instead, we reset after the user is 
          // done resizing the window and releases the resize bars, which 
          // sends a WM_EXITSIZEMOVE message.
        }
        else // API call such as SetWindowPos or mSwapChain->SetFullscreenState.
        {
          OnResize();
        }
      }
    }
    return 0;

    // WM_EXITSIZEMOVE is sent when the user grabs the resize bars.
  case WM_ENTERSIZEMOVE:
    mAppPaused = true;
    mResizing  = true;
    mTimer.Stop();
    return 0;

    // WM_EXITSIZEMOVE is sent when the user releases the resize bars.
    // Here we reset everything based on the new window dimensions.
  case WM_EXITSIZEMOVE:
    mAppPaused = false;
    mResizing  = false;
    mTimer.Start();
    OnResize();
    return 0;

    // WM_DESTROY is sent when the window is being destroyed.
  case WM_DESTROY:
    PostQuitMessage(0);
    return 0;

    // The WM_MENUCHAR message is sent when a menu is active and the user presses 
    // a key that does not correspond to any mnemonic or accelerator key. 
  case WM_MENUCHAR:
    // Don't beep when we alt-enter.
    return MAKELRESULT(0, MNC_CLOSE);

    // Catch this message so to prevent the window from becoming too small.
  case WM_GETMINMAXINFO:
    ((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
    ((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200; 
    return 0;

  case WM_LBUTTONDOWN:
  case WM_MBUTTONDOWN:
  case WM_RBUTTONDOWN:
    OnMouseDown(msg, wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;
  case WM_LBUTTONUP:
  case WM_MBUTTONUP:
  case WM_RBUTTONUP:
    OnMouseUp(msg, wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;
  case WM_MOUSEWHEEL:
    OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam));
    return 0;
  case WM_MOUSEMOVE:
    OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;
  case WM_KEYUP:
    OnKeyUp(wParam);
    return 0;
  }

  return DefWindowProc(hwnd, msg, wParam, lParam);
}

bool D3DApp::InitMainWindow()
{
  WNDCLASS wc;
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = MainWndProc; 
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = mhAppInst;
  wc.hIcon         = LoadIcon(mhAppInst, MAKEINTRESOURCE(IDI_ICON1));
  wc.hCursor       = LoadCursor(0, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
  wc.lpszMenuName  = 0;
  wc.lpszClassName = L"D3DWndClassName";

  if( !RegisterClass(&wc) )
  {
    MessageBox(0, L"RegisterClass Failed.", 0, 0);
    return false;
  }

  // Compute window rectangle dimensions based on requested client 
  // area dimensions.
  RECT R = { 0, 0, mClientWidth, mClientHeight };
  AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
  int width  = R.right - R.left;
  int height = R.bottom - R.top;

  mhMainWnd = CreateWindow(L"D3DWndClassName", mMainWndCaption.c_str(), 
    WS_OVERLAPPEDWINDOW | WS_SYSMENU | WS_CLIPCHILDREN,
    CW_USEDEFAULT, CW_USEDEFAULT, width, height,
    0, 0, mhAppInst, 0); 
  if( !mhMainWnd )
  {
    MessageBox(0, L"CreateWindow Failed.", 0, 0);
    return false;
  } 

  ShowWindow(mhMainWnd, SW_SHOW);
  UpdateWindow(mhMainWnd);

  return true;
}

bool D3DApp::InitDirect3D()
{
  // Create the device and device context.

  UINT createDeviceFlags = 0;
//#if defined(DEBUG) || defined(_DEBUG)  
//  createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
//#endif

  wprintf(L"Video adapters:\n");

  IDXGIFactory* factory = 0;
  CreateDXGIFactory(__uuidof(IDXGIFactory),(void**)&factory);

  IDXGIAdapter* adapter = 0;
  DXGI_ADAPTER_DESC desc;
  LARGE_INTEGER version;

  int nrAdapters = 0;
  while(true)
  {
    factory->EnumAdapters(nrAdapters,&adapter);
    if(!adapter){
      break;
    }

    adapter->GetDesc(&desc);
    wprintf(desc.Description);

    nrAdapters++;
  }

  wprintf(L"\n");

  D3D_FEATURE_LEVEL featureLevel;
  HRESULT hr = D3D11CreateDevice(
    0,                 // default adapter
    md3dDriverType,
    0,                 // no software device
    createDeviceFlags, 
    0, 0,              // default feature level array
    D3D11_SDK_VERSION,
    &md3dDevice,
    &featureLevel,
    &md3dImmediateContext);

  if(FAILED(hr))
  {
    MessageBox(0, L"D3D11CreateDevice Failed.", 0, 0);
    return false;
  }

  string directFeature;
  switch(featureLevel)
  {
    case D3D_FEATURE_LEVEL::D3D_FEATURE_LEVEL_9_1:
      directFeature = "9.1";
      break;
    case D3D_FEATURE_LEVEL::D3D_FEATURE_LEVEL_9_2:
      directFeature = "9.2";
      break;
    case D3D_FEATURE_LEVEL::D3D_FEATURE_LEVEL_9_3:
      directFeature = "9.3";
      break;
    case D3D_FEATURE_LEVEL::D3D_FEATURE_LEVEL_10_0:
      directFeature = "10.0";
      break;
    case D3D_FEATURE_LEVEL::D3D_FEATURE_LEVEL_10_1:
      directFeature = "10.1";
      break;
    case D3D_FEATURE_LEVEL::D3D_FEATURE_LEVEL_11_0:
      directFeature = "11.0";
      break;
    default:
      directFeature = "undefined";
      break;
  }
  printf("Feature level: DirectX %s\n", directFeature.c_str());

  // Check 4X MSAA quality support for our back buffer format.
  // All Direct3D 11 capable devices support 4X MSAA for all render 
  // target formats, so we only need to check quality support.
  HR(md3dDevice->CheckMultisampleQualityLevels(
    DXGI_FORMAT_R8G8B8A8_UNORM, 4, &m4xMsaaQuality));
  assert( m4xMsaaQuality > 0 );

  // Fill out a DXGI_SWAP_CHAIN_DESC to describe our swap chain.
  DXGI_SWAP_CHAIN_DESC sd;
  sd.BufferDesc.Width  = mClientWidth;
  sd.BufferDesc.Height = mClientHeight;
  sd.BufferDesc.RefreshRate.Numerator = 60;
  sd.BufferDesc.RefreshRate.Denominator = 1;
  sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
  sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
  sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

  // Use 4X MSAA? 
  if( mEnable4xMsaa )
  {
    sd.SampleDesc.Count   = 4;
    sd.SampleDesc.Quality = m4xMsaaQuality-1;
  }
  // No MSAA
  else
  {
    sd.SampleDesc.Count   = 1;
    sd.SampleDesc.Quality = 0;
  }

  sd.BufferUsage  = DXGI_USAGE_RENDER_TARGET_OUTPUT;
  sd.BufferCount  = 1;
  sd.OutputWindow = mhMainWnd;
  sd.Windowed     = true;
  sd.SwapEffect   = DXGI_SWAP_EFFECT_DISCARD;
  sd.Flags        = 0;

  // To correctly create the swap chain, we must use the IDXGIFactory that was
  // used to create the device.  If we tried to use a different IDXGIFactory instance
  // (by calling CreateDXGIFactory), we get an error: "IDXGIFactory::CreateSwapChain: 
  // This function is being called with a device from a different IDXGIFactory."

  IDXGIDevice* dxgiDevice = 0;
  HR(md3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice));

  IDXGIAdapter* dxgiAdapter = 0;
  HR(dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&dxgiAdapter));

  IDXGIFactory* dxgiFactory = 0;
  HR(dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory));

  HR(dxgiFactory->CreateSwapChain(md3dDevice, &sd, &mSwapChain));
  mSwapChain->SetFullscreenState(mFullscreen, 0);

  ReleaseCOM(dxgiDevice);
  ReleaseCOM(dxgiAdapter);
  ReleaseCOM(dxgiFactory);

  // The remaining steps that need to be carried out for d3d creation
  // also need to be executed every time the window is resized.  So
  // just call the OnResize method here to avoid code duplication.

  OnResize();

  return true;
}

void D3DApp::RefreshGlobals()
{
  Gine::gDevice = md3dDevice;
  Gine::gContext = md3dImmediateContext;
  Gine::gRenderTargetView = mRenderTargetView;
  Gine::gDepthStencilView = mDepthStencilView;
  Gine::gScreenW = mClientWidth;
  Gine::gScreenH = mClientHeight;
  if(Gine::gCamera)
    Gine::gCamera->SetAspect((float)mClientWidth / mClientHeight);
}

void D3DApp::CalculateFrameStats()
{
  // Code computes the average frames per second, and also the 
  // average time it takes to render one frame.  These stats 
  // are appended to the window caption bar.

  static int frameCnt = 0;
  static float timeElapsed = 0.0f;

  frameCnt++;

  // Compute averages over one second period.
  if( (mTimer.TotalTime() - timeElapsed) >= 1.0f )
  {
    float fps = (float)frameCnt; // fps = frameCnt / 1
    float mspf = 1000.0f / fps;

    std::wostringstream outs;   
    outs.precision(6);
    outs << mMainWndCaption << L"    "
      << L"FPS: " << fps << L"    " 
      << L"Frame Time: " << mspf << L" (ms)";
    SetWindowText(mhMainWnd, outs.str().c_str());

    // Reset for next average.
    frameCnt = 0;
    timeElapsed += 1.0f;
  }
}


